package com.derbysoft.dswitch.adapter.ihg.dswitch3.service.impl;

import com.derbysoft.dswitch.adapter.ihg.common.domain.ChannelAuthorization;
import com.derbysoft.dswitch.adapter.ihg.common.domain.Status;
import com.derbysoft.dswitch.adapter.ihg.common.log.LogBody;
import com.derbysoft.dswitch.adapter.ihg.common.log.PerformanceLogger;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ChannelAuthorizationRepository;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ReservationRepository;
import com.derbysoft.dswitch.adapter.ihg.common.utils.EnvironmentUtils;
import com.derbysoft.dswitch.adapter.ihg.common.utils.ErrorMessageUtils;
import com.derbysoft.dswitch.adapter.ihg.dswitch3.HotelProviderService;
import com.derbysoft.dswitch.adapter.ihg.dswitch3.translate.Translator;
import com.derbysoft.dswitch.adapter.ihg.dto.Error;
import com.derbysoft.dswitch.adapter.ihg.dto.*;
import com.derbysoft.dswitch.adapter.ihg.exception.ChannelNotFoundException;
import com.derbysoft.dswitch.adapter.ihg.exception.InvokeProviderSideException;
import com.derbysoft.dswitch.adapter.ihg.webservice.handler.WebServiceHandler;
import com.derbysoft.dswitch.adapter.ihg.webservice.impl.AccountProvider;
import com.derbysoft.dswitch.dto.common.KeyValue;
import com.derbysoft.dswitch.dto.hotel.reservation.CancelInfoDTO;
import com.derbysoft.dswitch.dto.hotel.reservation.HotelCancelRS;
import com.derbysoft.dswitch.remote.hotel.dto.AbstractRQ;
import com.derbysoft.dswitch.remote.hotel.dto.ErrorDTO;
import com.derbysoft.dswitch.remote.hotel.dto.HotelCancelRequest;
import com.derbysoft.dswitch.remote.hotel.dto.HotelCancelResponse;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

/**
 * Created by: jason
 * Date: 2012-05-17
 */
@Component("hotelCancelService")
public class HotelCancelServiceImpl extends BaseHotelService
        implements HotelProviderService<HotelCancelRequest, HotelCancelResponse> {

    private static final String CANCELLED = "Cancel";

    @Autowired
    @Qualifier("bookWebServiceHandler")
    private WebServiceHandler<OTACancelRQ, OTACancelRS> cancelWebService;

    @Autowired
    protected ChannelAuthorizationRepository channelAuthorizationRepository;

    @Autowired
    private ReservationRepository reservationRepository;

    @Override
    public HotelCancelResponse invoke(HotelCancelRequest cancelRequest) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            HotelCancelResponse cancelResponse = new HotelCancelResponse();
            cancelResponse.setTaskId(cancelRequest.getHeader().getTaskId());
            String confirmNumber = reservationRepository.findConfirmNumber(cancelRequest.getHotelCancelRQ().getErsp());
            if (confirmNumber == null) {
                reservationRepository.updateStatus(cancelRequest.getHotelCancelRQ().getErsp(), Status.CANCEL_FAILED);
                cancelResponse.setError(createErrorDTO(cancelRequest));
                return cancelResponse;
            }

            OTACancelRQ otaCancelRQ = createOTACancelRQ(confirmNumber, cancelRequest);

            OTACancelRS otaCancelRS = cancelWebService.invoke(otaCancelRQ, true, AccountProvider.of(EnvironmentUtils.getIhgCancelUrl(),
                    loadAuthorization(cancelRequest.getHeader().getSource())));
            stopWatch.stop();
            long webserviceTime = stopWatch.getTotalTimeMillis();

            if (otaCancelRS.getErrors() != null) {
                reservationRepository.updateStatus(cancelRequest.getHotelCancelRQ().getErsp(), Status.CANCEL_FAILED);
                cancelResponse.setError(createCancelFailedError(otaCancelRS));
                cancelResponse.getElapsedTimes().add(new KeyValue(ElapsedTimeType.CANCEL.name(), String.valueOf(webserviceTime)));
                return cancelResponse;
            }
            updateReservationItem(cancelRequest.getHotelCancelRQ().getErsp(), otaCancelRS);
            cancelResponse.setHotelCancelRS(createHotelCancelRS(otaCancelRS));
            cancelResponse.getElapsedTimes().add(new KeyValue(ElapsedTimeType.CANCEL.name(), String.valueOf(webserviceTime)));
            return cancelResponse;
        } catch (Exception ex) {
            if (stopWatch.isRunning()) {
                stopWatch.stop();
            }
            String webserviceTime = String.valueOf(stopWatch.getTotalTimeMillis());
            HotelCancelResponse failedResponse = failedResponse(cancelRequest, ex);
            failedResponse.getElapsedTimes().add(new KeyValue(ElapsedTimeType.CANCEL.name(), String.valueOf(webserviceTime)));
            PerformanceLogger.append(LogBody.ERROR_CODE, failedResponse.getError().getCode());
            return failedResponse;
        }
    }

    private void updateReservationItem(String ersp, OTACancelRS otaCancelRS) {
        String cancelNumber = otaCancelRS.getCancelInfoRS().getUniqueID().getID();
        reservationRepository.cancelReservationItem(ersp, cancelNumber);
    }

    private HotelCancelRS createHotelCancelRS(OTACancelRS otaCancelRS) {
        HotelCancelRS hotelCancelRS = new HotelCancelRS();
        CancelInfoDTO cancelInfo = new CancelInfoDTO();
        cancelInfo.setHotelCancellationNO(otaCancelRS.getCancelInfoRS().getUniqueID().getID());
        hotelCancelRS.setCancelInfo(cancelInfo);
        return hotelCancelRS;
    }

    private ErrorDTO createCancelFailedError(OTACancelRS otaCancelRS) {
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setSource(provider);
        errorDTO.setCode(otaCancelRS.getErrors().getError().get(0).getCode());
        if (otaCancelRS.getErrors() != null && CollectionUtils.isNotEmpty(otaCancelRS.getErrors().getError())) {
            StringBuilder builder = new StringBuilder();
            for (Error error : otaCancelRS.getErrors().getError()) {
                builder.append("ErrorCode: ").append(error.getCode());
                builder.append(", ").append("ShortText: ").append(error.getShortText());
                errorDTO.setMessage(builder.toString());
            }
        }
        return errorDTO;
    }

    private ErrorDTO createErrorDTO(HotelCancelRequest cancelRequest) {
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setSource(adapter);
        errorDTO.setCode("CancelFailed");
        errorDTO.setMessage(String.format("CancelFailed ersp = [%s] : Reservation not found or confirm number not found !!",
                cancelRequest.getHotelCancelRQ().getErsp()));
        return errorDTO;
    }

    private OTACancelRQ createOTACancelRQ(String confirmNumber, HotelCancelRequest cancelRequest) {
        OTACancelRQ otaCancelRQ = new OTACancelRQ();
        setRequestAttributes(otaCancelRQ, cancelRequest);
        otaCancelRQ.setCancelType(CANCELLED);

        UniqueId uniqueId10 = new UniqueId();
        uniqueId10.setID(confirmNumber);
        uniqueId10.setType("10");
        uniqueId10.setInstance("PNR");
        otaCancelRQ.getUniqueID().add(uniqueId10);
        return otaCancelRQ;
    }

    private HotelCancelResponse failedResponse(HotelCancelRequest request, Exception ex) {
        HotelCancelResponse response = new HotelCancelResponse();
        response.setTaskId(request.getHeader().getTaskId());
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setCode(SYSTEM);
        errorDTO.setSource(ex instanceof InvokeProviderSideException ? provider : adapter);
        errorDTO.setMessage(ErrorMessageUtils.buildErrorMessage(ex));
        response.setError(errorDTO);
        return response;
    }

    protected void setRequestAttributes(Request otaRequest, AbstractRQ derbyRequest) {
        otaRequest.setPOS(createPOS(derbyRequest.getHeader().getSource()));
        otaRequest.setVersion(Translator.VERSION);
    }

    private POS createPOS(String sourceCode) {
        POS pos = new POS();
        Source source = new Source();
        RequestorId requestorID = new RequestorId();
        requestorID.setID(loadByDerbyCode(sourceCode).getIataNumber());
        requestorID.setType(Translator.REQUEST_TYPE);
        source.setRequestorID(requestorID);
        pos.setSource(source);
        return pos;
    }

    protected ChannelAuthorization loadByDerbyCode(String channelPassport) {
        ChannelAuthorization channel = channelAuthorizationRepository.loadByDerbyChannelCode(channelPassport);
        if (channel == null) {
            throw new ChannelNotFoundException(channelPassport);
        }
        return channel;
    }
}
